#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include "tagfs.h"

// return output to tagid, create new tagid if not exist
void get_tagid_list(char** tag, unsigned int* tagid, int num_tag) 
{
	FILE* fp;
	struct tagid_name* entries;
	char system_filepath[256];
	unsigned int num, free_index;
	int i, j;
	get_system_filepath("/.tagid_tagname", system_filepath);
	fp = fopen(system_filepath, "rb");
	if(fp == NULL)
		fp = fopen(system_filepath, "wb+");
	// read the whole file to array, insert new entry ,and write back to the file
	// get the number of entries
	fseek(fp, 0, SEEK_END);
	num = ftell(fp)/sizeof(struct tagid_name);
	entries = (struct tagid_name*)malloc(sizeof(struct tagid_name) * (num+num_tag)); // allocate for the whole map + new entry
	fseek(fp, 0, SEEK_SET);
	fread(entries, sizeof(struct tagid_name), num, fp);
	for(i=0; i<num_tag; i++)
	{
		for(j=0; j<num; j++)
		{
			if(!strcmp(entries[j].name, tag[i]))
			{
				tagid[i] = entries[j].id;
				break;
			}
		}
		// does not have this tag before
		if(j==num)
		{
			// find available tagid from a gap between tagid
			for(j=0; j<num; j++)
			{
				if(entries[j].id != j)
				{
					break;
				}
			}
			free_index = j;
			// copy result
			tagid[i] = free_index;
			// move the rest of a file to create a space for new entry
			if(free_index != num)
				memmove(&entries[free_index+1], &entries[free_index], (num - free_index) * sizeof(struct tagid_name));
			// insert the tag into the space
			entries[free_index].id = free_index;		
			strcpy(entries[free_index].name, tag[i]);
			num++;
		}
	}
	fclose(fp);
	// write entries back to the file
	fp = fopen(system_filepath, "wb");
	fwrite(entries, sizeof(struct tagid_name), num, fp);
	fclose(fp);
	free(entries);
}

// insert inode to .inode_name file if it does not exist
void add_inode(unsigned int inode, char* fullpath)
{
	FILE* fp;
	struct inode_name* entries;
	char system_filepath[256];
	unsigned int num, free_index;
	int i, j;
	get_system_filepath("/.inode_name", system_filepath);
	fp = fopen(system_filepath, "rb");
	if(fp == NULL)
		fp = fopen(system_filepath, "wb+");
	// read the whole file to array, insert new entry ,and write back to the file
	// get the number of entries
	fseek(fp, 0, SEEK_END);
	num = ftell(fp)/sizeof(struct inode_name);
	entries = (struct inode_name*)malloc(sizeof(struct inode_name) * (num+1)); // allocate for the whole map + new entry
	fseek(fp, 0, SEEK_SET);
	fread(entries, sizeof(struct inode_name), num, fp);
	for(i=0; i<=num; i++)
	{
		// reture if already has map
		if(entries[i].inode == inode)
		{
			break;
		}
		// does not have this inode before so add it in order of inode number
		else if(entries[i].inode > inode || i == num)
		{
			memmove(&entries[i+1], &entries[i], (num - i) * sizeof(struct inode_name));
			entries[i].inode = inode;
			strcpy(entries[i].name, fullpath);
			num++;
			break;
		}
	}
	fclose(fp);
	// write entries back to the file
	fp = fopen(system_filepath, "wb");
	fwrite(entries, sizeof(struct inode_name), num, fp);
	fclose(fp);
	free(entries);
}

void add_inode_to_tagid(unsigned int inode, unsigned int tagid)
{
	FILE* fp;
	unsigned int *entries; // entries include size, tagid, inode number
	char system_filepath[256];
	unsigned int num;
	int i, j, size;
	int start_index, length;
	get_system_filepath("/.tagid_inode", system_filepath);
	fp = fopen(system_filepath, "rb");
	if(fp == NULL)
		fp = fopen(system_filepath, "wb+");
	// find the number of entries
	fseek(fp, 0, SEEK_END);
	num = ftell(fp)/sizeof(unsigned int);
	entries = (unsigned int*)malloc(sizeof(unsigned int) * (num+3)); // might need to add up to three entries if tagid doesn't exist
	fseek(fp, 0, SEEK_SET);
	fread(entries, sizeof(unsigned int), num, fp);
	// find start index to write & the number of entries to write (length)
	i = 0;
	// default value used when the file has no entry
	start_index = 0;
	length = 3;
	while(i < num)
	{	
		if(entries[i+1] == tagid)
		{
			size = entries[i];
			j = 1; // skip the first one which is tagid
			while(j < size-1)
			{
				if(entries[i+j+1] == inode)
				{
					return; // there is already this inode to the tagid
				}
				else if(entries[i+j+1] > inode)
				{
					break; // already find a place to insert new inode
				}
				else
				{
					j++;
				}
			}
			start_index = i+j+1;
			length = 1; // need one space for adding new inode
			// increment size
			entries[i]++;
			break;
		}
		else if(entries[i+1] > tagid) // means there is no tagid in the entries since the entries is sorted by tagid
		{
			start_index = i;
			length = 3; // need three spaces for adding new tagid
			break;
		}
		else
		{
			i += entries[i]; // skip to the next entry by adding the size of current entry
			start_index = i; // set new start index which will be used if it already reach the end of entries
			length = 3;
		}
	}
	// move entries to create space
	memmove(&entries[start_index+length], &entries[start_index], (num - start_index) * sizeof(unsigned int));
	if(length == 1)
	{
		entries[start_index] = inode;
	}
	else if(length == 3)
	{
		entries[start_index] = 3; // size include size(itself), tagid, inode
		entries[start_index+1] = tagid;
		entries[start_index+2] = inode;
	}
	fclose(fp);
	// write entries back to the file
	fp = fopen(system_filepath, "wb");
	fwrite(entries, sizeof(unsigned int), num+length, fp);
	fclose(fp);
	free(entries);
}

void add_tagid_to_inode(unsigned int tagid, unsigned int inode)
{
	FILE* fp;
	unsigned int *entries; // entries include size, inode number, tagid
	char system_filepath[256];
	unsigned int num;
	int i, j, size;
	int start_index, length;
	get_system_filepath("/.inode_tagid", system_filepath);
	fp = fopen(system_filepath, "rb");
	if(fp == NULL)
		fp = fopen(system_filepath, "wb+");
	// find the number of entries
	fseek(fp, 0, SEEK_END);
	num = ftell(fp)/sizeof(unsigned int);
	entries = (unsigned int*)malloc(sizeof(unsigned int) * (num+3)); // might need to add up to three entries if tagid doesn't exist
	fseek(fp, 0, SEEK_SET);
	fread(entries, sizeof(unsigned int), num, fp);
	// find start index to write & the number of entries to write (length)
	i = 0;
	// default value used when the file has no entry
	start_index = 0;
	length = 3;
	while(i < num)
	{	
		if(entries[i+1] == inode)
		{
			size = entries[i];
			j = 1; // skip the first one which is tagid
			while(j < size-1)
			{
				if(entries[i+j+1] == tagid)
				{
					return; // there is already this inode to the tagid
				}
				else if(entries[i+j+1] > tagid)
				{
					break; // already find a place to insert new inode
				}
				else
				{
					j++;
				}
			}
			start_index = i+j+1;
			length = 1; // need one space for adding new inode
			// increment size
			entries[i]++;
			break;
		}
		else if(entries[i+1] > inode) // means there is no inode in the entries since the entries is sorted by inode
		{
			start_index = i;
			length = 3; // need three spaces for adding new tagid
			break;
		}
		else
		{
			i += entries[i]; // skip to the next entry by adding the size of current entry
			start_index = i; // set new start index which will be used if it already reach the end of entries
			length = 3;
		}
	}
	// move entries to create space
	memmove(&entries[start_index+length], &entries[start_index], (num - start_index) * sizeof(unsigned int));
	if(length == 1)
	{
		entries[start_index] = tagid;
	}
	else if(length == 3)
	{
		entries[start_index] = 3; // size include size(itself), tagid, inode
		entries[start_index+1] = inode;
		entries[start_index+2] = tagid;
	}
	fclose(fp);
	// write entries back to the file
	fp = fopen(system_filepath, "wb");
	fwrite(entries, sizeof(unsigned int), num+length, fp);
	fclose(fp);
	free(entries);
}

int main(int argc, char *argv[])
{
	FILE* fp;
	int i;
	int num_tag;
	int res;
	char* filename;
	char fullpath[256];
	char** tag;
	unsigned int* tagid;
	
	num_tag = argc-2;
	filename = argv[1];
	// get the absolute path for the file as it is needed to find its inode number
	getcwd(fullpath, 256);
	strcat(fullpath, "/");
	strcat(fullpath, filename);
	clean_path(fullpath); // clean . and ..

	tag = (char**)malloc(num_tag*sizeof(char*));
	for(i=2; i<argc; i++)
	{
		tag[i-2] = argv[i];
	}
	
	tagid = (unsigned int*)malloc(num_tag*sizeof(unsigned int));
	
	// get file inode
	struct stat *statbuf = (struct stat*)malloc(sizeof(struct stat));
	res = lstat(fullpath, statbuf);
	if(res)
	{
		printf("file does not exist!!!\n");
		return 1;
	}
	// get the list of tagid from tag name from .tagid_tagname file
	get_tagid_list(tag, tagid, num_tag);
	// insert inode to .inode_name file if it does not exist
	add_inode(statbuf->st_ino, fullpath);
	// insert new inode to each tagid in .tagid_inode file
	for(i=0; i<num_tag; i++)
	{
		add_inode_to_tagid(statbuf->st_ino, tagid[i]);
	}
	// insert new tagids to the inode in .inode_tagid file
	for(i=0; i<num_tag; i++)
	{
		add_tagid_to_inode(tagid[i], statbuf->st_ino);
	}	

	//print_tagid_tagname();
	//print_inode_name();
	//print_tagid_inode();
	//print_inode_tagid();
	
	return 1;
}
